package model;

import util.Pair;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Alex Vikharev vikharev@yandex-team.ru
 *         created 23.02.12
 */
public class GameRecord {
    private final List<Position> firstPlayerMoves;
    private final List<Position> secondPlayerMoves;
    private final GameState result;

    private GameRecord(final List<Position> firstPlayerMoves, final List<Position> secondPlayerMoves, final GameState result) {
        this.firstPlayerMoves = firstPlayerMoves;
        this.secondPlayerMoves = secondPlayerMoves;
        this.result = result;
    }

    public GameState getResult() {
        return result;
    }

    public int getMovesCount() {
        return firstPlayerMoves.size() + secondPlayerMoves.size();
    }

    public byte[] toBytes() {
        byte[] result = new byte[16];
        Field field = Field.builder().build();
        Field newField;
        for (int i = 0; i < getMovesCount(); i++) {
            int moveNum = i / 2;
            newField = (i % 2 == 0) ? firstPlayerMoves.get(moveNum).getField() : secondPlayerMoves.get(moveNum).getField();
            final Pair<Integer, Integer> diff = Field.getFirstDiffPos(newField, field);
            int figureCode = newField.get(diff.getFirst(), diff.getSecond()).code;
            result[i] = (byte) ((figureCode << 4) + (diff.getFirst() << 2) + diff.getSecond());
            field = newField;
        }
        return result;
    }

    public static GameRecord fromBytes(byte[] bytes, final GameType gameType) {
        final GameRecordBuilder builder = builder(gameType);
        Field field = Field.builder().build();
        for (int i = 0; i < bytes.length; i++) {
            byte move = bytes[i];
            Figure figure = Figure.byCode( (move >> 4) & 0b1111);
            int x = (move >> 2) & 0b11;
            int y = move & 0b11;
            Field newField = field.addFigure(x, y, figure);
            Figure nextFigure = newField.isGameOver(gameType) ? null : (i + 1 >= bytes.length ? null : Figure.byCode((bytes[i + 1] >> 4) & 0b1111));
            if (i % 2 == 0) {
                builder.firstPlayerMoves(new Position(newField, nextFigure));
            } else {
                builder.secondPlayerMoves(new Position(newField, nextFigure));
            }
            if(nextFigure == null){
                break;
            }
            field = newField;
        }
        return builder.build();
    }

    public static GameRecordBuilder builder(final GameType gameType) {
        return new GameRecordBuilder(gameType);
    }

    //No defensive coping. Not safe, but faster.
    public List<Position> getFirstPlayerMoves() {
        return firstPlayerMoves;
    }

    //Same note as for previous getter.
    public List<Position> getSecondPlayerMoves() {
        return secondPlayerMoves;
    }

    public static class GameRecordBuilder {
        private final List<Position> firstPlayerMoves = new ArrayList<>();
        private final List<Position> secondPlayerMoves = new ArrayList<>();
        private GameState state;
        private GameType gameType;

        public GameRecordBuilder(final GameType gameType) {
            this.gameType = gameType;
        }

        public GameRecord build() {
            return new GameRecord(firstPlayerMoves, secondPlayerMoves, state);
        }

        public GameRecordBuilder firstPlayerMoves(Position move) {
            firstPlayerMoves.add(move);
            if (move.getField().isGameOver(gameType)) {
                state = GameState.FIRST_PLAYER_WIN;
            }
            if (move.getField().getUsedFigures().size() == 16) {
                state = GameState.TIE;
            }
            return this;
        }

        public GameRecordBuilder secondPlayerMoves(Position move) {
            secondPlayerMoves.add(move);
            if (move.getField().isGameOver(gameType)) {
                state = GameState.SECOND_PLAYER_WIN;
            }
            if (move.getField().getUsedFigures().size() == 16) {
                state = GameState.TIE;
            }
            return this;
        }


    }
}
